<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Yougo Symfony 1.4.x</title>
</head>
<body>
<img id="logo-ineat" src="http://blog.ineat-conseil.fr/wp-content/themes/ineatconseil/images/logo_default.png" alt="logo-ineat" />

<!-- Your Slides -->
<!-- One section is one slide -->

<section>
  <script type="text/javascript" src="http://vis.stanford.edu/protovis/protovis-r3.2.js"></script>
  <div style="padding:0 128px"><div id="fig" style="width: 768px; height: 768px;"> 
    <script type="text/javascript+protovis"> 
      
      var fsec = pv.Format.date("%S s"),
          fmin = pv.Format.date("%M m"),
          fhou = pv.Format.date("%H h"),
          fwee = pv.Format.date("%a"),
          fdat = pv.Format.date("%d d"),
          fmon = pv.Format.date("%b"),
          radius = 768 / 2;
 
      /* Generate the fields for the given date. */
      function fields() {
        var d = new Date();
 
        function days() {
          return 32 - new Date(d.getYear(), d.getMonth(), 32).getDate();
        }
 
        var second = (d.getSeconds() + d.getMilliseconds() / 1000) / 60;
        var minute = (d.getMinutes() + second) / 60;
        var hour = (d.getHours() + minute) / 24;
        var weekday = (d.getDay() + hour) / 7;
        var date = (d.getDate() - 1 + hour) / days();
        var month = (d.getMonth() + date) / 12;
 
        return [
            { value: second,  index: .7, text: fsec(d) },
            { value: minute,  index: .6, text: fmin(d) },
            { value: hour,    index: .5, text: fhou(d) },
            { value: weekday, index: .3, text: fwee(d) },
            { value: date,    index: .2, text: fdat(d) },
            { value: month,   index: .1, text: fmon(d) },
          ];
      }
 
      var vis = new pv.Panel()
          .width(radius * 2)
          .height(radius * 2);
 
      vis.add(pv.Wedge)
          .data(fields)
          .left(radius)
          .bottom(radius)
          .innerRadius(function(d) radius * d.index)
          .outerRadius(function(d) radius * (d.index + .1))
          .startAngle(-Math.PI / 2)
          .angle(function(d) 2 * Math.PI * d.value)
          .fillStyle(function(d) "hsl(" + (360 * d.value - 180) + ", 50%, 50%)")
          .lineWidth(4)
          .strokeStyle("#222")
        .anchor("end").add(pv.Label)
          .font("bold 12px sans-serif")
          .textStyle("#FFF")
          .textMargin(7)
          .text(function(d) d.text);
 
      setInterval(function() vis.render(), 50);
 
    </script>
  </div></div>
</section>

<section>
  <h1>YouGO : Symfony 1.4.x</h1>
  <img id="logo-symfony" src="http://symfony.com/logos/symfony_white_02.png" alt="logo-symfony" />
  <footer>Hubert SABLONNIÈRE</footer>
</section>

<section>
  <h2>Au menu ce soir...</h2>
  
  <ul class="menu">
    <li>YouGO ?</li>
    <li>symfony : c'est quoi ?</li>
    <li>symfony + YouGO = &hearts;</li>
    <li>Reste à faire</li>
    <li>Autres fonctionnalités</li>
    <li>Démo</li>
    <li>Conclusion</li>
  </ul>
</section>

<section>
  <h1 class="center">YouGO ?</h1>
</section>

<section>
  <h2>YouGO ?</h2>
  
  <ul class="center">
    <li>Comparer des frameworks de "front"</li>
    <li>Demandes de congés</li>
    <li>Backend commun</li>
    <li>Services RESTful</li>
  </ul>
</section>

<section>
  <h1 class="center">admin == false</h1>
</section>

<section>
  <figure>
    <img class="screenshot" src="./bmml/login.png" alt="" />
  </figure>
</section>

<section>
  <figure>
    <img class="screenshot" src="./bmml/requests-my.png" alt="" />
  </figure>
</section>

<section>
  <figure>
    <img class="screenshot" src="./bmml/requests-add.png" alt="" />
  </figure>
</section>

<section>
  <figure>
    <img class="screenshot" src="./bmml/password-change.png" alt="" />
  </figure>
</section>

<section>
  <h1 class="center">admin == true</h1>
</section>

<section>
  <figure>
    <img class="screenshot" src="./bmml/users-list.png" alt="" />
  </figure>
</section>

<section>
  <figure>
    <img class="screenshot" src="./bmml/users-add-edit.png" alt="" />
  </figure>
</section>

<section>
  <figure>
    <img class="screenshot" src="./bmml/user-types-list.png" alt="" />
  </figure>
</section>

<section>
  <figure>
    <img class="screenshot" src="./bmml/user-types-add-edit.png" alt="" />
  </figure>
</section>

<section>
  <figure>
    <img class="screenshot" src="./bmml/request-types-list.png" alt="" />
  </figure>
</section>

<section>
  <figure>
    <img class="screenshot" src="./bmml/request-types-add-edit.png" alt="" />
  </figure>
</section>

<section>
  <figure>
    <img class="screenshot" src="./bmml/requests-validation.png" alt="" />
  </figure>
</section>

<section>
  <figure>
    <img class="screenshot" src="./bmml/requests-validation-action.png" alt="" />
  </figure>
</section>

<section>
  <h2>YouGO : les représentations</h2>
  
  <ul class="center">
    <li>User</li>
    <li>UserType</li>
    <li>Request</li>
    <li>RequestType</li>
    <li>DetailedUser</li>
  </ul>
</section>

<section>
  <h2>YouGO : les services</h2>
  
  <table class="wikitable">
    <tbody>
      <tr><td> <strong>Method</strong></td><td><strong>Path</strong></td><td><strong>Description</strong></td></tr>
      <tr><td>GET</td><td>/users</td><td>Récupère tous les utilisateurs</td></tr>
      <tr><td>GET</td><td>/users/{id}</td><td>Récupère un utilisateur</td></tr>
      <tr><td>POST</td><td>/users</td><td>Ajoute un utilisateur</td></tr>
      <tr><td>PUT</td><td>/users/{id}</td><td>Met à jour un utilisateur</td></tr>
      <tr><td>DELETE</td><td>/users/{id}</td><td>Supprimer un utilisateur</td></tr>
      <tr class="color"><td>GET</td><td>/users/{email}</td><td>Récupère un utilisateur, permet également de tester la connexion</td></tr>
      <tr class="color"><td>PUT</td><td>/users/{id}/password</td><td>Met à jour un mot de passe</td></tr>
    </tbody>
  </table>
</section>

<section>
  <h1 class="center">symfony : c'est quoi ?</h1>
</section>

<section>
  <h2>symfony : c'est quoi ?</h2>
    
  <dl class="simple">
    <dt>Nom</dt>
      <dd>Symfony</dd>
    <dt>Société</dt>
      <dd>Sensio Labs (Paris)</dd>
    <dt>Date de création</dt>
      <dd>octobre 2005</dd>
    <dt>Dernière version stable</dt>
      <dd>1.4.11 (mars 2011)</dd>
    <dt>Futur, roadmap</dt>
      <dd>2.0 beta3 (refonte majeure)</dd>
  </dl>
</section>
    
<section>
  <h2>symfony : c'est quoi ?</h2>
    
  <dl class="simple">
    <dt>Langage</dt>
      <dd>PHP (>= 5.2.4)</dd>
    <dt>Plateforme cible</dt>
      <dd>PHP + Apache (ou lighttpd...)</dd>
    <dt>License</dt>
      <dd>MIT</dd>
    <dt>Documentation</dt>
      <dd>API, tutos (Jobeet !), références</dd>
    <dt>Livres</dt>
      <dd>136 (amazon.fr)</dd>
    <dt>Ressemblances, origines</dt>
      <dd>Rails, Django...</dd>
  </dl>
</section>

<section>
  <h1 class="center">symfony + YouGO = &hearts;</h1>
</section>

<section>
  <h2>Il était une fois...</h2>
  
  <pre class="brush: bash; toolbar: false; highlight: [3,10]">mkdir yougo-project
cd yougo-project
mkdir -p lib/vendor
cd lib/vendor
wget http://www.symfony-project.org/get/symfony-1.4.11.tgz
tar xvzf symfony-1.4.11.tgz
mv symfony-1.4.11 symfony
rm symfony-1.4.11.tgz
cd ../..
php lib/vendor/symfony/data/bin/symfony generate:project yougo</pre>
</section>

<section>
  <h2>Squelette de base</h2>
  
  <pre class="brush: plain; toolbar: false; highlight: [9,18]">yougo-project
├── apps
├── cache
├── config
├── data
├── lib
│   ├── ...
│   └── vendor
│       └── symfony					librairie du framework
├── log
├── plugins
├── test
├── web
│   ├── ...
│   ├── css
│   ├── images
│   └── js
└── symfony							ligne de commande (script php)</pre>
</section>

<section>
  <h2>Consommation des services ?</h2>
  
  <dl class="detailed">
    <dt>RESTful</dt>
      <dd>avec un plugin symfony</dd>
      <dd>(et un peu de dev)</dd>
    <dt>SOAP</dt>
      <dd>Extension PHP avec <code>SoapClient</code></dd>
      <dd>(et un peu de dev)</dd>
  </dl>
</section>

<section>
  <h2>Les plugins symfony : où ?</h2>
  
  <pre class="brush: plain; toolbar: false;">http://www.symfony-project.org/plugins</pre>
  <iframe src="http://www.symfony-project.org/plugins/" width="100%" height="475px"></iframe>
</section>

<section>
  <h2>Les plugins symfony : installation</h2>
  
  <pre class="brush: plain; toolbar: false;">./symfony plugin:install sfWebBrowserPlugin</pre>
  <pre class="brush: plain; toolbar: false; highlight: [6,11,15]">yougo-project
├── apps
├── cache
├── config
│   ├── ...
│   └── ProjectConfiguration.class.php
├── data
├── lib
├── log
├── plugins
│   └── sfWebBrowserPlugin
│       ├── ...
│       └── lib
│           ├── ...
│           └── sfWebBrowser.class.php
├── test
├── web
└── symfony</pre>
</section>

<section>
  <h2 class="small">sfWebBrowserPlugin ?</h2>
  
  <pre class="brush: php; toolbar: false;">// ./plugins/sfWebBrowserPlugin/lib/sfWebBrowser.class.php
  
class sfWebBrowser {
  /*...*/
  
  public function __construct($defaultHeaders = array(), $adapterClass = null, $adapterOptions = array()) {/*...*/}
  
  /** @return sfWebBrowser */
  public function get($uri, $parameters = array(), $headers = array()) {/*...*/}
  
  /** @return sfWebBrowser */
  public function post($uri, $parameters = array(), $headers = array()) {/*...*/}
  
  /** @return sfWebBrowser */
  public function put($uri, $parameters = array(), $headers = array()) {/*...*/}
  
  /** @return sfWebBrowser */
  public function delete($uri, $parameters = array(), $headers = array()) {/*...*/}

  public function getResponseCode() {/*...*/}

  public function getResponseText() {/*...*/}
  
  /*...*/
}</pre>
</section>

<section>
  <h2>Utilisation du plugin</h2>
  
  <pre class="brush: php; toolbar: false;">$b = new sfWebBrowser(array('Content-Type', 'application/json'));
$b->get('http://localhost:8080/yougo-rest/api/users/1');
echo $b->getResponseCode();
echo $b->getResponseText();</pre>
  
  <pre class="brush: php; toolbar: false;">200
{
  "active": true,
  "admin": true,
  "email": "kristina.chung@company.com",
  "fullName": "Kristina CHUNG",
  "id": 1,
  "typeId": 1
}</pre>
</section>

<section>
  <h2>Abstraction d'appel des services</h2>
  
  <pre class="brush: plain; toolbar: false; highlight: [8,11]">yougo-project
├── apps
├── cache
├── config
├── data
├── lib
│   ├── ...
│   └── rest
│       ├── YougoClientException.class.php
│       ├── YougoException.class.php
│       ├── YougoRestClient.class.php
│       └── YougoServerException.class.php
├── log
├── plugins
├── test
├── web
└── symfony</pre>
</section>

<section>
  <pre class="brush: php; toolbar: false; highlight: [5,6,7,14,25]">// ./lib/rest/YougoRestClient.class.php

class YougoRestClient {
  private static $instance; private $sfWebBrowser;
  private $serverUrl = 'http://localhost:8080';
  private $apiPath = '/yougo-rest/api';
  private $defaultHeaders = array('Content-Type' => 'application/json');

  /* constructor(...), singleton(), init(...) */
  
  /** @return sfWebBrowser */
  public function get($uri, $parameters = null, $headers = array()) {
    try {
      $b = $this->sfWebBrowser->get($this->serverUrl . $this->apiPath . $uri, $parameters, $headers);
      $this->debug('GET', $this->serverUrl . $this->apiPath . $uri, $parameters, $headers, $b->getResponseCode());
    } catch (Exception $e) {
      $this->serverAccessError($e);
    }

    if ($b->getResponseCode() >= 500)
      $this->serverError($b);
    if ($b->getResponseCode() >= 400)
      $this->clientError($b);

    return $b;
  }
  
  /* put(...), post(...), delete(...), debug(...), 
    serverAccessError(...), serverError(...), clientError(...) */
}</pre>
</section>

<section>
  <h2>Utilisation du YougoRestClient</h2>
  
  <pre class="brush: php; toolbar: false;">$b = YougoRestClient::singleton()->get('/users/1');
echo $b->getResponseCode();
echo $b->getResponseText();</pre>
  
  <pre class="brush: php; toolbar: false;">200
{
  "active": true,
  "admin": true,
  "email": "kristina.chung@company.com",
  "fullName": "Kristina CHUNG",
  "id": 1,
  "typeId": 1
}</pre>
</section>

<section>
  <h1 class="center">&lt;Couche model&gt;</h1>
</section>

<section>
  <h2>ORM</h2>
    
  <dl class="detailed">
    <dt>Doctrine (1.2)</dt>
      <dd>Requêtes DQL</dd>
      <dd>Migrations version schéma (up/down)</dd>
      <dd>Fixtures (données de test)</dd>
      <dd class="color">Déclaration de schéma</dd>
      <dd class="color">Génération de code</dd>
  </dl>
</section>

<section>
  <h2>Déclaration de schéma</h2>
  
    <pre class="brush: plain; toolbar: false; highlight: [7]">yougo-project
├── apps
├── cache
├── config
│   ├── ...
│   └── doctrine
│       └── schema.xml
├── data
├── lib
├── log
├── plugins
├── test
├── web
└── symfony</pre>
</section>

<section>
  <h2>Représentations</h2>
  
  <ul class="menu">
    <li>User</li>
    <li>UserType</li>
    <li>Request</li>
    <li>RequestType</li>
    <li class="color">DetailedUser</li>
  </ul>
</section>

<section>
  <pre class="brush: plain; toolbar: false;">// ./config/doctrine/schema.yml
  
Request:
  columns:
    'from': { type: date, notnull: true }
    'to': { type: date, notnull: true }
    askComment: string(255)
    answerComment: string(255)
    typeId: { type: integer(5), notnull: true }
    status:
      type: enum
      values: [ACCEPTED, PENDING, REFUSED]
      default: PENDING

RequestType:
  columns:
    description: { type: string(255), notnull: true }

User:
  columns:
    fullName: { type: string(255), notnull: true }
    email: { type: string(255), notnull: true }
    admin:  boolean
    active: boolean
    typeId: { type: integer(5), notnull: true }

UserType:
  columns:
    description: { type: string(255), notnull: true }</pre>
</section>

<section>
  <h2 class="small">Génération de code</h2>
  
  <pre class="brush: plain; toolbar: false;">./symfony doctrine:build --model</pre>
  
  <pre class="brush: plain; toolbar: false; highlight : [11,13,15,17]">yougo-project
├── apps
├── cache
├── config
├── data
├── lib
│   ├── ...
│   └── model
│       └── doctrine
│           ├── base
│           ├── Request.class.php
│           ├── RequestTable.class.php
│           ├── RequestType.class.php
│           ├── RequestTypeTable.class.php
│           ├── User.class.php
│           ├── UserTable.class.php
│           ├── UserType.class.php
│           └── UserTypeTable.class.php
├── log
├── plugins
├── test
├── web
└── symfony</pre>
</section>

<section>
  <h2>Code généré :-(</h2>
  
  <pre class="brush: php; toolbar: false; highlight : [3]">// ./lib/model/RequestType.class.php
  
class RequestType extends BaseRequestType {
}</pre>
</section>

<section>
  <pre class="brush: php; toolbar: false; highlight : [7,9,10]">// ./lib/model/base/BaseRequestType.class.php
  
/**
 * BaseRequestType
 * This class has been auto-generated by the Doctrine ORM Framework
 * 
 * @property string $description
 * 
 * @method string      getDescription() Returns the current record's "description" value
 * @method RequestType setDescription() Sets the current record's "description" value
 * 
 * @package    front-symfony-1.4.x
 * @subpackage model
 * @author     Your name here
 * @version    SVN: $Id: Builder.php 7490 2010-03-29 19:53:27Z jwage $
 */
abstract class BaseRequestType extends sfDoctrineRecord {
  public function setTableDefinition() {
    $this->setTableName('request_type');
    $this->hasColumn('description', 'string', 255, array(
      'type' => 'string',
      'notnull' => true,
      'length' => 255,
    ));
  }

  public function setUp() {
    parent::setUp();
  }
}</pre>
</section>

<section>
  <pre class="brush: php; toolbar: false;">// ./lib/model/base/BaseUser.class.php
  
/**
 * BaseUser
 * This class has been auto-generated by the Doctrine ORM Framework
 * 
 * @property string $fullName
 * @property string $email
 * @property boolean $admin
 * @property boolean $active
 * @property integer $typeId
 * 
 * @method string  getFullName() Returns the current record's "fullName" value
 * @method string  getEmail()    Returns the current record's "email" value
 * @method boolean getAdmin()    Returns the current record's "admin" value
 * @method boolean getActive()   Returns the current record's "active" value
 * @method integer getTypeId()   Returns the current record's "typeId" value
 * @method User    setFullName() Sets the current record's "fullName" value
 * @method User    setEmail()    Sets the current record's "email" value
 * @method User    setAdmin()    Sets the current record's "admin" value
 * @method User    setActive()   Sets the current record's "active" value
 * @method User    setTypeId()   Sets the current record's "typeId" value
 * 
 * @package    front-symfony-1.4.x
 * @subpackage model
 * @author     Your name here
 * @version    SVN: $Id: Builder.php 7490 2010-03-29 19:53:27Z jwage $
 */
abstract class BaseUser extends sfDoctrineRecord {/*...*/}</pre>
</section>

<section>
  <h2>Une dernière pour la route...</h2>
  
  <pre class="brush: php; toolbar: false;">// ./lib/model/DetailedUser.class.php
  
class DetailedUser extends User {
    
    public $requests;

    public function getRequests() {
        return $this->requests;
    }

    public function setRequests($requests) {
        $this->requests = $requests;
    }

}</pre>
</section>

<section>
  <h2>Exemples</h2>

  <pre class="brush: php; toolbar: false;">$requestType = new RequestType();
$requestType->description = 'Congé payé';
$requestType->setDescription('Congé payé');
$requestType->set('description', 'Congé payé');</pre>

  <pre class="brush: php; toolbar: false;">$user = new User();
$user->fullName = 'Kristina CHUNG';
$user->setAdmin(true);
$user->set('email', 'kristina.chung@company.com');</pre>
</section>

<section>
  <h1 class="center">&lt;/Couche model&gt;</h1>
</section>

<section>
  <h2>Utilisation du YougoRestClient</h2>
  
  <pre class="brush: php; toolbar: false;">$b = YougoRestClient::singleton()->get('/users/1');
echo $b->getResponseCode();
echo $b->getResponseText();</pre>
  
  <pre class="brush: php; toolbar: false;">200
{
  "active": true,
  "admin": true,
  "email": "kristina.chung@company.com",
  "fullName": "Kristina CHUNG",
  "id": 1,
  "typeId": 1
}</pre>
</section>

<section>
  <h2>YouGO : les services</h2>
  
  <table class="wikitable">
    <tbody>
      <tr><td> <strong>Method</strong></td><td><strong>Path</strong></td><td><strong>Description</strong></td></tr>
      <tr><td>GET</td><td>/users</td><td>Récupère tous les utilisateurs</td></tr>
      <tr><td>GET</td><td>/users/{id}</td><td>Récupère un utilisateur</td></tr>
      <tr><td>POST</td><td>/users</td><td>Ajoute un utilisateur</td></tr>
      <tr><td>PUT</td><td>/users/{id}</td><td>Met à jour un utilisateur</td></tr>
      <tr><td>DELETE</td><td>/users/{id}</td><td>Supprimer un utilisateur</td></tr>
      <tr class="color"><td>GET</td><td>/users/{email}</td><td>Récupère un utilisateur, permet également de tester la connexion</td></tr>
      <tr class="color"><td>PUT</td><td>/users/{id}/password</td><td>Met à jour un mot de passe</td></tr>
    </tbody>
  </table>
</section>

<section>
  <h2 class="small">I have a dream...</h2>
  
  <pre class="brush: php; toolbar: false; highlight : [7,8,16,17,19]">$user = new User();

$user->setFullName('Kristina CHUNG');
$user->setEmail('kristina.chung@company.com');
$user->setActive(true);

$id = UserRestRessource::singleton()->create($user);
$user = UserRestRessource::singleton()->find($id);

echo $user->fullName;
echo $user->email;
echo $user->active;

$user->setActive(false);

UserRestRessource::singleton()->update($user);
UserRestRessource::singleton()->delete($user->id);

$users = UserRestRessource::singleton()->findAll();</pre>

<pre class="brush: php; toolbar: false;">Kristina CHUNG
kristina.chung@company.com
true</pre>
</section>

<section>
  <pre class="brush: php; toolbar: false; highlight: [4,5,10,20]">// ./lib/rest/RestRessource.class.php

abstract class RestResource {
  protected $resourcePath;  // ex: '/users'
  protected $resourceClass; // ex: 'Users'

  protected function __construct($resourceClass, $resourcePath) {/*...*/}

  public function find($id) {
    $b = YougoRestClient::singleton()->get($this->resourcePath.'/'.$id);
    return $this->jsonToObject($b->getResponseText());
  }

  /* findAll(), findAllAndMap($key, $value), 
    create($resource), update($resource), delete($id), 
    objectToJson($obj), objectToFormUrlEncoded($array) */

  protected function jsonToObject($json) {
    $resourceArray = json_decode($json);
    $resource = new $this->resourceClass;
    foreach ($resourceArray as $key => $value) {
      $resource->set($key, $value);
    }
    return $resource;
  }

  /* jsonToObjectList($json) */
}</pre>
</section>

<section>
  <pre class="brush: php; toolbar: false;">// ./lib/rest/UserRestRessource.class.php
  
class UserRestResource extends RestResource {

  private static $instance;

  protected function __construct() {
    parent::__construct('User', '/users');
  }

  /**
  * @return UserRestResource
  */
  public static function singleton() {
    if (!isset(self::$instance)) {
      self::$instance = new UserRestResource();
    }
    return self::$instance;
  }

  public function updatePassword($userId, $passwords) {
    $b = YougoRestClient::singleton()->put(
      $this->resourcePath.'/'.$userId.'/password', 
      $this->objectToFormUrlEncoded($passwords), 
      array('Content-Type' => 'application/x-www-form-urlencoded')
    );
  }
}</pre>
</section>

<section>
  <h2>Abstraction d'appel des services</h2>
  
  <pre class="brush: plain; toolbar: false; highlight: [9,10,11,12,13,14]">yougo-project
├── apps
├── cache
├── config
├── data
├── lib
│   ├── ...
│   └── rest
│       ├── DetailedUserRestResource.class.php
│       ├── RequestRestResource.class.php
│       ├── RequestTypeRestResource.class.php
│       ├── RestResource.class.php
│       ├── UserRestResource.class.php
│       ├── UserTypeRestResource.class.php
│       ├── YougoClientException.php
│       ├── YougoException.php
│       ├── YougoRestClient.class.php
│       └── YougoServerException.php
├── log
├── plugins
├── test
├── web
└── symfony</pre>
</section>

<section>
  <h2>symfony + YouGO = &hearts;</h2>

  <dl class="detailed">
    <dt>Résumé 1/2</dt>
      <dd>Génération du projet</dd>
      <dd>Installation d'un plugin (services)</dd>
      <dd>Utilisation de l'ORM et du schema.yml</dd>
      <dd>Génération des classes "model"</dd>
      <dd>Abstraction d'appel des services</dd>
  </dl>
</section>

<section>
  <h2 class="small">Génération de l'application</h2>
	
  <pre class="brush: plain; toolbar: false;">./symfony generate:app main</pre>
  
  <pre class="brush: plain; toolbar: false; highlight: [3,6,21,22]">yougo-project
├── apps
│   └── main
│       ├── config
│       │   ├── ...
│       │   └── routing.yml
│       ├── i18n
│       ├── lib
│       ├── modules
│       └── templates
│           └── layout.php
├── cache
├── config
├── data
├── lib
├── log
├── plugins
├── test
├── web
│   ├── ...
│   ├── index.php
│   └── main_dev.php
└── symfony</pre>
</section>

<section>
  <h2>Les routes</h2>

  <pre class="brush: plain; toolbar: false; highlight: [5,11,15];"># You can find more information about this file on the symfony website:
# http://www.symfony-project.org/reference/1_4/en/10-Routing

# default rules
homepage:
  url:   /
  param: { module: default, action: index }

# generic rules
# please, remove them by adding more specific rules
default_index:
  url:   /:module
  param: { action: index }

default:
  url:   /:module/:action/*</pre>
</section>

<section>
  <h2 class="small">Test</h2>

  <pre class="brush: plain; toolbar: false;">http://yougo-demo.localhost/index.php/</pre>

  <figure>
    <img src="http://www.symfony-project.org/images/jobeet/1_4/01/congratulations.png" alt="default module" />
  </figure>
</section>

<section>
  <h1 class="center">&lt;formulaires&gt;</h1>
</section>

<section>
  <h2 class="small">Génération des classes "form"</h2>
	
  <pre class="brush: plain; toolbar: false;">./symfony doctrine:build --forms</pre>
  
  <pre class="brush: plain; toolbar: false; highlight: [13,14,15,16]">yougo-project
├── ...
├── lib
│   ├── form
│   │   ├── BaseForm.class.php
│   │   └── doctrine
│   │       ├── base
│   │       │   ├── BaseRequestForm.class.php
│   │       │   ├── BaseRequestTypeForm.class.php
│   │       │   ├── BaseUserForm.class.php
│   │       │   └── BaseUserTypeForm.class.php
│   │       ├── BaseFormDoctrine.class.php
│   │       ├── RequestForm.class.php
│   │       ├── RequestTypeForm.class.php
│   │       ├── UserForm.class.php
│   │       └── UserTypeForm.class.php
│   ├── model
│   ├── rest
│   └── vendor
├── log
├── plugins
├── test
├── web
└── symfony</pre>
</section>

<section>
  <h2>Code généré :-(</h2>
  
  <pre class="brush: php; toolbar: false; highlight: [13,14,15,16]">// ./lib/form/doctrine/UserForm.class.php
  
class UserForm extends BaseUserForm {
  
  public function configure() {
  }
  
}</pre>
</section>

<section>
  <pre class="brush: php; toolbar: false; highlight: []">// ./lib/form/doctrine/base/BaseUserForm.class.php
  
abstract class BaseUserForm extends BaseFormDoctrine {
  public function setup() {
    $this->setWidgets(array(
      'id'       => new sfWidgetFormInputHidden(),
      'fullName' => new sfWidgetFormInputText(),
      'email'    => new sfWidgetFormInputText(),
      'admin'    => new sfWidgetFormInputCheckbox(),
      'active'   => new sfWidgetFormInputCheckbox(),
      'typeId'   => new sfWidgetFormInputText(),
    ));
    $this->setValidators(array(
      'id'       => new sfValidatorChoice(array(
          'choices'     => array($this->getObject()->get('id')), 
          'empty_value' => $this->getObject()->get('id'), 
          'required'    => false)),
      'fullName' => new sfValidatorString(array('max_length' => 255)),
      'email'    => new sfValidatorString(array('max_length' => 255)),
      'admin'    => new sfValidatorBoolean(array('required' => false)),
      'active'   => new sfValidatorBoolean(array('required' => false)),
      'typeId'   => new sfValidatorInteger(),
    ));
    /*...*/
  }

  public function getModelName() {
    return 'User';
  }
}</pre>
</section>

<section>
  <h1 class="center">&lt;/formulaires&gt;</h1>
</section>

<section>
  <h2 class="small">Génération du module "CRUD" users</h2>
	
  <pre class="brush: plain; toolbar: false;">./symfony doctrine:generate-module main users User</pre>
  
  <pre class="brush: plain; toolbar: false; highlight: [10]">yougo-project
├── apps
│   └── main
│       ├── config
│       │   ├── ...
│       │   └── routing.yml
│       ├── i18n
│       ├── lib
│       ├── modules
│       │   └── users
│       │       ├── actions
│       │       │   └── actions.class.php
│       │       └── templates
│       │           ├── editSuccess.php
│       │           ├── _form.php
│       │           ├── indexSuccess.php
│       │           └── newSuccess.php
│       └── templates
│           └── layout.php
└── ...</pre>
</section>

<section>
  <pre class="brush: php; toolbar: false; highlight: [5,22,23]">// ./apps/main/modules/users/actions/actions.class.php

class usersActions extends sfActions {
  public function executeIndex(sfWebRequest $request) {
    $this->users = Doctrine_Core::getTable('User')->createQuery('a')->execute();
  }

  public function executeNew(sfWebRequest $request) {
    $this->form = new UserForm();
  }

  public function executeCreate(sfWebRequest $request) {
    $this->forward404Unless($request->isMethod(sfRequest::POST));

    $this->form = new UserForm();
    $this->processForm($request, $this->form);

    $this->setTemplate('new');
  }

  public function executeEdit(sfWebRequest $request) {
    $user = Doctrine_Core::getTable('User')
      ->find(array($request->getParameter('id')));
    $this->forward404Unless($user, sprintf('Object user does not exist (%s).', $request->getParameter('id')));
    $this->form = new UserForm($user);
  }</pre>
</section>

<section>
  <pre class="brush: php; toolbar: false; highlight: [29,30,40,41,43,51]; first-line: 27">public function executeUpdate(sfWebRequest $request) {
    $this->forward404Unless($request->isMethod(sfRequest::POST) || $request->isMethod(sfRequest::PUT));
    $user = Doctrine_Core::getTable('User')
      ->find(array($request->getParameter('id')));
    $this->forward404Unless($user, sprintf('Object user does not exist (%s).', $request->getParameter('id')));
    $this->form = new UserForm($user);
    $this->processForm($request, $this->form);
    $this->setTemplate('edit');
  }

  public function executeDelete(sfWebRequest $request) {
    $request->checkCSRFProtection();

    $user = Doctrine_Core::getTable('User')
      ->find(array($request->getParameter('id')));
    $this->forward404Unless($user, sprintf('Object user does not exist (%s).', $request->getParameter('id')));
    $user->delete();

    $this->redirect('users/index');
  }

  protected function processForm(sfWebRequest $request, sfForm $form) {
    $form->bind($request->getParameter($form->getName()), $request->getFiles($form->getName()));
    if ($form->isValid()) {
      $user = $form->save();
      $this->redirect('users/edit?id='.$user->getId());
    }
  }
}</pre>
</section>

<section>
  <h1 class="center">ORM => Services RESTful</h1>
</section>

<section>
  <pre class="brush: php; toolbar: false; highlight: [5,22,23]">// ./apps/main/modules/users/actions/actions.class.php

class usersActions extends sfActions {
  public function executeIndex(sfWebRequest $request) {
    $this->users = UserRestResource::singleton()->findAll();
  }

  public function executeNew(sfWebRequest $request) {
    $this->form = new UserForm();
  }

  public function executeCreate(sfWebRequest $request) {
    $this->forward404Unless($request->isMethod(sfRequest::POST));

    $this->form = new UserForm();
    $this->processForm($request, $this->form);

    $this->setTemplate('new');
  }

  public function executeEdit(sfWebRequest $request) {
    $user = UserRestResource::singleton()
      ->find($request->getParameter('id'));
    $this->forward404Unless($user, sprintf('Object user does not exist (%s).', $request->getParameter('id')));
    $this->form = new UserForm($user);
  }</pre>
</section>

<section>
  <pre class="brush: php; toolbar: false; highlight: [29,30,39,47,49]; first-line: 27">public function executeUpdate(sfWebRequest $request) {
    $this->forward404Unless($request->isMethod(sfRequest::POST) || $request->isMethod(sfRequest::PUT));
    $user = UserRestResource::singleton()
      ->find($request->getParameter('id'));
    $this->forward404Unless($user, sprintf('Object user does not exist (%s).', $request->getParameter('id')));
    $this->form = new UserForm($user);
    $this->processForm($request, $this->form);
    $this->setTemplate('edit');
  }

  public function executeDelete(sfWebRequest $request) {
    $request->checkCSRFProtection();
    UserRestResource::singleton()->delete($request->getParameter('id'));
    $this->redirect('users/index');
  }

  protected function processForm(sfWebRequest $request, sfForm $form) {
    $form->bind($request->getParameter($form->getName()), $request->getFiles($form->getName()));
    if ($form->isValid()) {
      if ($form->getValue('id') == null)
        $id = UserRestResource::singleton()->create($form->getValues());
      else {
        UserRestResource::singleton()->update($form->getValues());
        $id = $form->getValue('id');
      }
      $this->redirect('users/edit?id='.$id);
    }
  }
}</pre>
</section>

<section>
  <h2 class="small">Test</h2>

  <pre class="brush: plain; toolbar: false;">http://yougo-demo.localhost/index.php/users</pre>
  
  <iframe src="http://yougo-demo.localhost/index.php/users" width="100%" height="475px" style="background:#FFF"></iframe>
</section>

<section>
  <figure>
    <img class="screenshot" src="./bmml/users-list.png" alt="" />
  </figure>
</section>

<section>
  <h2 class="small">Test</h2>

  <pre class="brush: plain; toolbar: false;">http://yougo-demo.localhost/main_dev.php/users/edit?id=1</pre>
  
  <iframe src="http://yougo-demo.localhost/main_dev.php/users/edit?id=1" width="100%" height="475px" style="background:#FFF"></iframe>
</section>

<section>
  <figure>
    <img class="screenshot" src="./bmml/users-add-edit.png" alt="" />
  </figure>
</section>

<section>
  <h2>Vues : Layout, Template, Partial...</h2>
  
  <pre class="brush: plain; toolbar: false; highlight: [13,18]">yougo-project
├── apps
│   └── main
│       ├── config
│       │   ├── ...
│       │   └── routing.yml
│       ├── i18n
│       ├── lib
│       ├── modules
│       │   └── users
│       │       ├── actions
│       │       │   └── actions.class.php
│       │       └── templates
│       │           ├── editSuccess.php
│       │           ├── _form.php
│       │           ├── indexSuccess.php
│       │           └── newSuccess.php
│       └── templates
│           └── layout.php
└── ...</pre>
</section>

<section>
  <h2>Layout commun</h2>
  
  <pre class="brush: xml; toolbar: false; highlight: [];"><!-- ./apps/main/templates/layout.php -->
  
&lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"&gt;
&lt;html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"&gt;
  &lt;head&gt;
    &lt;?php include_http_metas() ?&gt;
    &lt;?php include_metas() ?&gt;
    &lt;?php include_title() ?&gt;
    &lt;link rel="shortcut icon" href="/favicon.ico" /&gt;
    &lt;?php include_stylesheets() ?&gt;
    &lt;?php include_javascripts() ?&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;?php echo $sf_content ?&gt;
  &lt;/body&gt;
&lt;/html&gt;</pre>
</section>

<section>
  <pre class="brush: xml; toolbar: false; highlight: [];"><!-- ./apps/main/modules/users/templates/indexSuccess.php -->
  
&lt;h1&gt;Users List&lt;/h1&gt;
&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Id&lt;/th&gt;
      &lt;th&gt;Full name&lt;/th&gt;
      &lt;th&gt;Email&lt;/th&gt;
      &lt;th&gt;Admin&lt;/th&gt;
      &lt;th&gt;Active&lt;/th&gt;
      &lt;th&gt;Type&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;?php foreach ($users as $user): ?&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href="&lt;?php echo url_for('users/edit?id='.$user-&gt;getId()) ?&gt;"&gt;
        &lt;?php echo $user-&gt;getId() ?&gt;
      &lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;&lt;?php echo $user-&gt;getFullName() ?&gt;&lt;/td&gt;
      &lt;td&gt;&lt;?php echo $user-&gt;getEmail() ?&gt;&lt;/td&gt;
      &lt;td&gt;&lt;?php echo $user-&gt;getAdmin() ?&gt;&lt;/td&gt;
      &lt;td&gt;&lt;?php echo $user-&gt;getActive() ?&gt;&lt;/td&gt;
      &lt;td&gt;&lt;?php echo $user-&gt;getTypeId() ?&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;?php endforeach; ?&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;a href="&lt;?php echo url_for('users/new') ?&gt;"&gt;New&lt;/a&gt;</pre>
</section>

<section>
  <h2>Template "new" et "edit"</h2>

  <pre class="brush: xml; toolbar: false; highlight: [];"><!-- ./apps/main/modules/users/templates/newSuccess.php -->
  
&lt;h1&gt;New User&lt;/h1&gt;

&lt;?php include_partial('form', array('form' =&gt; $form)) ?&gt;</pre>
  <pre class="brush: xml; toolbar: false; highlight: [];"><!-- ./apps/main/modules/users/templates/editSuccess.php -->
  
&lt;h1&gt;Edit User&lt;/h1&gt;

&lt;?php include_partial('form', array('form' =&gt; $form)) ?&gt;</pre>
</section>

<section>
  <pre class="brush: xml; toolbar: false; highlight: [];"><!-- ./apps/main/modules/users/templates/_form.php -->
  
&lt;?php use_stylesheets_for_form($form) ?&gt;
&lt;?php use_javascripts_for_form($form) ?&gt;

&lt;form action="&lt;?php echo url_for('users/'.($form-&gt;getObject()-&gt;isNew() ? 'create' : 'update').(!$form-&gt;getObject()-&gt;isNew() ? '?id='.$form-&gt;getObject()-&gt;getId() : '')) ?&gt;" method="post" &lt;?php $form-&gt;isMultipart() and print 'enctype="multipart/form-data" ' ?&gt;&gt;
&lt;?php if (!$form-&gt;getObject()-&gt;isNew()): ?&gt;
&lt;input type="hidden" name="sf_method" value="put" /&gt;
&lt;?php endif; ?&gt;
  &lt;table&gt;
    &lt;tfoot&gt;
      &lt;tr&gt;
        &lt;td colspan="2"&gt;
          &lt;?php echo $form-&gt;renderHiddenFields(false) ?&gt;
          &amp;nbsp;&lt;a href="&lt;?php echo url_for('users/index') ?&gt;"&gt;Back to list&lt;/a&gt;
          &lt;?php if (!$form-&gt;getObject()-&gt;isNew()): ?&gt;
            &amp;nbsp;&lt;?php echo link_to('Delete', 'users/delete?id='.$form-&gt;getObject()-&gt;getId(), array('method' =&gt; 'delete', 'confirm' =&gt; 'Are you sure?')) ?&gt;
          &lt;?php endif; ?&gt;
          &lt;input type="submit" value="Save" /&gt;
        &lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tfoot&gt;
    &lt;tbody&gt;
      &lt;?php echo $form-&gt;renderGlobalErrors() ?&gt;
      &lt;tr&gt;
        &lt;th&gt;&lt;?php echo $form['fullName']-&gt;renderLabel() ?&gt;&lt;/th&gt;
        &lt;td&gt;
          &lt;?php echo $form['fullName']-&gt;renderError() ?&gt;
          &lt;?php echo $form['fullName'] ?&gt;
        &lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;th&gt;&lt;?php echo $form['email']-&gt;renderLabel() ?&gt;&lt;/th&gt;
        &lt;td&gt;
          &lt;?php echo $form['email']-&gt;renderError() ?&gt;
          &lt;?php echo $form['email'] ?&gt;
        &lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;th&gt;&lt;?php echo $form['admin']-&gt;renderLabel() ?&gt;&lt;/th&gt;
        &lt;td&gt;
          &lt;?php echo $form['admin']-&gt;renderError() ?&gt;
          &lt;?php echo $form['admin'] ?&gt;
        &lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;th&gt;&lt;?php echo $form['active']-&gt;renderLabel() ?&gt;&lt;/th&gt;
        &lt;td&gt;
          &lt;?php echo $form['active']-&gt;renderError() ?&gt;
          &lt;?php echo $form['active'] ?&gt;
        &lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;th&gt;&lt;?php echo $form['typeId']-&gt;renderLabel() ?&gt;&lt;/th&gt;
        &lt;td&gt;
          &lt;?php echo $form['typeId']-&gt;renderError() ?&gt;
          &lt;?php echo $form['typeId'] ?&gt;
        &lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;
&lt;/form&gt;</pre>
</section>

<section>
  <h2>symfony + YouGO = &hearts;</h2>

  <dl class="detailed">
    <dt>Résumé 2/2</dt>
      <dd>Génération de l'application</dd>
      <dd>Les routes (simples)</dd>
      <dd>Génération des classes "form"</dd>
      <dd>Génération du module "CRUD" users</dd>
      <dd>Adaptation : ORM => Services RESTful</dd>
      <dd>Vues : Layout, Template, Partial</dd>
  </dl>
</section>

<section>
    <h1 class="center">Reste à faire</h1>
</section>

<section>
  <h2>Reste à faire</h2>
  
  <dl class="detailed">
      <dd>Générer les trois autres modules</dd>
      <dd>Compléter le layout</dd>
      <dd>Compléter les templates</dd>
      <dd>Compléter les formulaires (validation)</dd>
      <dd>Créer les vues/calendriers</dd>
      <dd>Ajouter les styles CSS</dd>
  </dl>
</section>

<section>
  <h2>Reste à faire</h2>
    
  <dl class="detailed">
    <dt>Authentification / Sécurité</dt>
      <dd>Classe User ~ Session</dd>
      <dd>Gestion de "Credential"</dd>
      <dd>Plugin sfDoctrineGuard</dd>
  </dl>
</section>

<section>
  <h2>Reste à faire</h2>
    
  <dl class="detailed">
    <dt>Ajax</dt>
      <dd>Fonctions "helper"</dd>
      <dd>Quelques widgets</dd>
      <dd>Avec jQuery, c'est mieux ;-)</dd>
  </dl>
</section>

<section>
  <h2>Reste à faire</h2>
    
  <dl class="detailed">
    <dt>i18n/l10n</dt>
      <dd>Intégration native et complète</dd>
      <dd>Génération quasi-automatique</dd>
      <dd>Fichiers de traduction (ICU standard)</dd>
  </dl>
</section>


<section>
  <h2>Reste à faire</h2>
    
  <dl class="detailed">
    <dt>Tests</dt>
      <dd>Unitaires (avec lime)</dd>
      <dd>Fonctionnels (à la Selenium)</dd>
      <dd>Ligne de commande</dd>
  </dl>
</section>

<section>
    <h1 class="center">Fonctionnalités non abordées</h1>
</section>

<section>
  <h2>Fonctionnalités non abordées</h2>
    
  <dl class="detailed">
    <dt>Cache</dt>
      <dd>Configurations</dd>
      <dd>Vues (plusieurs niveaux)</dd>
  </dl>
</section>

<section>
  <h2>Fonctionnalités non abordées</h2>
    
  <dl class="detailed">
    <dt>Intégration avec outils de build</dt>
      <dd>?</dd>
  </dl>
</section>

<section>
  <h2>Fonctionnalités non abordées</h2>
    
  <dl class="detailed">
    <dt>Débuggage</dt>
      <dd>Barre de débug dans le navigateur</dd>
      <dd>XDebug</dd>
  </dl>
</section>

<section>
  <h2>Fonctionnalités non abordées</h2>
    
  <dl class="detailed">
    <dt>Intégration avec les IDE</dt>
      <dd>Plugin Netbeans (ligne de commande)</dd>
      <dd>Eclipse (mouais...)</dd>
  </dl>
</section>

<section>
  <a href="http://yougo.localhost/main_dev.php"><h1 class="center">Démo</h2></a>
</section>

<section>
  <h1 class="center">Conclusion</h2>
</section>

<section>
  <h2>Conclusion</h2>
  
  <dl class="detailed">
    <dt>Points forts</dt>
      <dd>Ligne de commande</dd>
      <dd>Génération de code</dd>
      <dd>Convention > configuration</dd>
      <dd>Système de vue</dd>
      <dd>Documentation</dd>
  </dl>
</section>

<section>
  <h2>Conclusion</h2>
  
  <dl class="detailed">
    <dt>Points faibles</dt>
      <dd>Code dans les vues</dd>
      <dd>Gestion des formulaires</dd>
      <dd>Pas assez de plugins de qualité</dd>
      <dd>Aspects "Entreprise"</dd>
      <dd>(outils de build, serialisation, WebServices)</dd>
  </dl>
</section>

<section>
  <h1 class="center">?</h2>
</section>





<!-- Your Style -->
<!-- Define the style of you presentation -->

<!-- Maybe a font from http://www.google.com/webfonts ? -->
<link href='http://fonts.googleapis.com/css?family=Oswald' rel='stylesheet' type='text/css'>
<script type="text/javascript" src="scripts/shCore.js"></script>
<script type="text/javascript" src="scripts/shBrushBash.js"></script>
<script type="text/javascript" src="scripts/shBrushJScript.js"></script>
<script type="text/javascript" src="scripts/shBrushPhp.js"></script>
<script type="text/javascript" src="scripts/shBrushPlain.js"></script>
<script type="text/javascript" src="scripts/shBrushXml.js"></script>
<link type="text/css" rel="stylesheet" href="styles/shCoreDefault.css"/>
<script type="text/javascript">SyntaxHighlighter.all();</script>

<style>
  html {}
  #logo-ineat {bottom: 5px;height:40px; position: absolute;}
  a { color: #82FF68; } a:hover {text-decoration: underline;}
  footer { position: absolute; bottom: 50px; right: 50px; color: #FAE50B; }
  strong {color: #82FF68}
  body {
      font-family: 'Oswald', arial, serif;
      background: url(http://unkool.com/wp-content/gallery/wallpapers/darkwood_floor_1920x1200.jpg);
      color: white;
      font-size: 2em;
  }

  /* Transition effect */
  section:not(.fixed) {
      -moz-transition: left 200ms linear 0s;
      -webkit-transition: left 200ms linear 0s;
      -o-transition: left 200ms linear 0s;
      -ms-transition: left 200ms linear 0s;
      transition: left 200ms linear 0s;
  }
  section { left: -150%; }
  section[aria-selected] { left: 0; }
  section[aria-selected] ~ section { left: +150% }

  .chapter { }
  .chapter h1 {line-height: 600px; vertical-align: middle; margin: 0; text-align: center; display: block}

  h1 {
    font-size: 65px;
    left: 50px;
    position: absolute;
    top: 50px;
  }
  h1.center {
    font-size: 100px;
    left: 0;
    text-align: center;
    top: 275px;
    width: 100%;
  }
  h2 {
      color: #FAE50B;
      margin: 70px 0 50px 0;
      font-size: 60px;
      text-align: center;
  }
  h2.small {
      margin: 20px 0 20px 0;
  }
  h2 .page {
    font-size: 25px;
  }
  
  ul.menu {
      border-left: 4px solid white;
      display: inline-block;
      font-size: 50px;
      margin: 0 0 0 50%;
      padding: 0 0 0 5%;
      text-align: left;
      width: 45%;
  }
  ul.center {
      display: inline-block;
      font-size: 50px;
      text-align: center;
      width: 100%;
  }
  ul.center li{
      padding: 10px 0;
  }
  
  dl.simple {
      font-size: 38px;
  }
  dl.simple dt, dl.simple dd {
    display: inline-block;
    padding: 5px 0;
  }
  dl.simple dt {
    color: #82FF68;
    font-weight: bold;
    text-align: right;
    width: 50%;
  }
  dl.simple dt:after {
    color: #FAE50B;
    content: ' : ';
  }
  dl.simple dd {
    text-align: left;
    width: 49%;
  }
  
  dl.detailed {
    margin:0 100px;
    width:600px;
  }
  dl.detailed dt, dl.detailed dd {
    display: inline-block;
  }
  dl.detailed dt {
    color: #82FF68;
    display: block;
    font-size: 40px;
    font-weight: bold;
    margin: 10px 0 20px 0;
  }
  dl.detailed dt:after {
    color: #FAE50B;
    content: ' : ';
  }
  dl.detailed dd {
    border-left: 4px solid #FAE50B;
    display: block;
    font-size: 35px;
    margin-left: 10px;
    padding: 10px 0 10px 25px;
  }
  
  table {
    border-collapse: collapse;
    border-spacing: 0;
  }
  table tr td {
    border-bottom:1px solid #FFF;
    border-top:1px solid #FFF;
    padding: 2px 5px;
  }
  table tr.color td {
    color: #FAE50B;
  }
  .color {
    color: #FAE50B;
  }
  
  q, p {
      margin: 50px auto 0 auto;
      width: 500px;
  }
  q:after {content: ""}
  q:before {content: ""}
  q {
      display: block;
      margin-top: 140px;
  }
  video {
      position: absolute;
      top: 210px;
      width: 260px;
      left: 445px;
      box-shadow: 0 0 10px black;
  }
  #arrow {
      position: absolute;
      top: 165px;
      left: 460px;
      font-size: 100px;
      color: white;

  }
  li {list-style-type: none}
  
  .syntaxhighlighter {
    font-size: 0.7em !important;
    max-height: 740px;
    padding: 5px 0;
  }
  
  figure {
    height: 100%;
    padding: 45px 0;
    text-align: center;
    vertical-align: middle;
    width: 100%;
  }

  /* slide 1 */
  #logo-symfony {
    margin: 336px 292px;
    width: 440px;
  }
</style>


<!-- {{{{ *****************  DZSlides CORE 2.0b1 *************************** -->
<!-- *********************************************************************** -->
<!-- *********************************************************************** -->
<!-- *********************************************************************** -->
<!-- *********************************************************************** -->

<!-- This block of code is not supposed to be edited, but if you want to change the behavior of the slides, feel free to hack it ;) -->

<!-- Default Style -->
<style>
  * { margin: 0; padding: 0; }
  details {display: none;}
  body {
    width: 1024px; height: 768px;
    margin-left: -512px; margin-top: -394px;
    position: absolute; top: 50%; left: 50%;
    overflow: hidden;
  }
  section {
    position: absolute;
    pointer-events: none;
    width: 100%; height: 100%;
  }
  section[aria-selected] { pointer-events: auto;}
  body {display: none}
  body.loaded {display: block}
</style>

<script>
  var friendWindows = [];
  var idx = 1;
  var slides;

  /* main() */

  window.onload = function() {
    slides = document.querySelectorAll("body > section");
    onhashchange();
    setSlide();
    document.body.className = "loaded";
    onresize();
  }

  /* Handle keys */

  window.onkeydown = function(e) {
    // Don't intercept keyboard shortcuts
    if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) {
      return;
    }
    if ( e.keyCode == 37 // left arrow
      || e.keyCode == 33 // page up
    ) {
      e.preventDefault();
      back();
    }
    if ( e.keyCode == 39 // right arrow
      || e.keyCode == 34 // page down
    ) {
      e.preventDefault();
      forward();
    }

    if ( e.keyCode == 32) { // space
        e.preventDefault();
        toggleContent();
    }
  }

  /* Adapt the size of the slides to the window */

  window.onresize = function() {
    var sx = document.body.clientWidth / window.innerWidth;
    var sy = document.body.clientHeight / window.innerHeight;
    var transform = "scale(" + (1/Math.max(sx, sy)) + ")";
    document.body.style.MozTransform = transform;
    document.body.style.WebkitTransform = transform;
    document.body.style.OTransform = transform;
    document.body.style.msTransform = transform;
    document.body.style.transform = transform;
  }
  function getDetails(idx) {
    var s = document.querySelector("section:nth-of-type("+ idx +")");
    var d = s.querySelector("details");
    return d?d.innerHTML:"";
  }
  window.onmessage = function(e) {
    msg = e.data;
    win = e.source;
    if (msg === "register") {
      friendWindows.push(win);
      win.postMessage(JSON.stringify({method: "registered", title: document.title, count: slides.length}), document.location);
      win.postMessage(JSON.stringify({method: "newslide", details: getDetails(idx), idx: idx}), document.location);
      return;
    }
    if (msg === "back") back();
    if (msg === "forward") forward();
    if (msg === "toggleContent") toggleContent();
    // setSlide(42)
    var r = /setSlide\((\d+)\)/.exec(msg);
    if (r) {
        idx = r[1];
        setSlide();
    }
  }

  /* If a Video is present in this new slide, play it.
     If a Video si present in the previous slide, stop it. */

  function toggleContent() {
    var s = document.querySelector("section[aria-selected]");
    if (s) {
        var video = s.querySelector("video");
        if (video) {
            if (video.ended || video.paused) {
                video.play();
            } else {
                video.pause();
            }
        }
    }
  }

  /* If the user change the slide number in the URL bar, jump
     to this slide. */

  window.onhashchange = function(e) {
    var newidx = ~~window.location.hash.split("#")[1];
    if (!newidx) newidx = 1;
    if (newidx == idx) return;
    idx = newidx;
    setSlide();
  }

  /* Slide controls */

  function back() {
    if (idx == 1) return;
    idx--;
    setSlide();
  }
  function forward() {
    if (idx >= slides.length) return;
    idx++;
    setSlide();
  }
  function setSlide() {
    var old = document.querySelector("section[aria-selected]");
    var next = document.querySelector("section:nth-of-type("+ idx +")");
    if (old) {
      old.removeAttribute("aria-selected");
      var video = old.querySelector("video");
      if (video) { video.pause(); }
    }
    if (next) {
      next.setAttribute("aria-selected", "true");
      var video = next.querySelector("video");
      if (video) { video.play(); }
    } else {
      console.warn("No such slide: " + idx);
      idx = 0;
      for (var i = 0; i < slides.length; i++) {
          if (slides[i].hasAttribute("aria-selected")) {
              idx = i + 1;
          }
      }
    }
    window.location.hash = idx;
    for (var i = 0; i < friendWindows.length; i++) {
        friendWindows[i].postMessage(JSON.stringify({method: "newslide", details: getDetails(idx), idx: idx}), document.location);
    }
  }
</script>
</body>
</html>
